#include "juvlmengine.h"
#include "private/jtouch_p.h"

#include <QTECore/jsingleton.h>
#include <QTECore/jlog.h>

#include <QGuiApplication>
#include <QPlainTextEdit>
#include <QTimerEvent>
#include <QMouseEvent>
#include <QTextEdit>
#include <QLineEdit>
#include <QPointer>
#include <QWidget>
#include <QLabel>
#include <QTimer>

#include <mutex>

namespace QTE
{

class Q_DECL_HIDDEN InBind : public QObject
{
public:
	InBind(QWidget *widget,
		   const std::function<void()> &stop,
		   const std::function<bool(const QPoint&)> &running,
		   const std::function<void(const QPoint&)> &actionTriggered,
		   const std::function<void(const QPoint&)> &inertanceStarted,
		   const int &refreshCycle);
	~InBind();

public:
	bool processEvent(QWidget *widget, QEvent *event);

public:
	bool isURLMing() const;
	void stopRun(bool delay = false);

protected:
	void timerEvent(QTimerEvent *event) override;

private:
	void mouseLeftButtonPressed(const QPoint &pos);
	void mouseLeftButtonReleased(const QPoint &pos);
	void mouseMove(const QPoint &pos);

private:
#if QT_VERSION < QT_VERSION_CHECK(6,4,0)
	void unfetters(QWidget *widget, const QPoint &pos = QPoint());
#else
	void unfetters(QWidget *widget, const QPoint &pos, const QPoint &globalPos);
	void unfetters(QWidget *widget) { unfetters(widget, QPoint(), QPoint()); }
#endif

	void startInertance();
	bool runExec();

private:
	const QWidget *m_widget;

	bool m_isPressed = false;
	bool m_runEnabled = false;
	int m_cMove = 0;

	QPoint m_lastPos;
	QPoint m_speed; //矢量

	int m_timerID = -1;
	bool m_delayStop = false;
	const int &m_refreshCycle;
	QPoint m_acceleratedSpeed; //矢量

	std::function<void()> mf_stop;
	std::function<bool(const QPoint&)> mf_running;
	std::function<void(const QPoint&)> mf_actionTriggered;
	std::function<void(const QPoint&)> mf_inertanceStarted;
};

inline InBind::InBind(QWidget *widget,
					  const std::function<void()> &stop,
					  const std::function<bool(const QPoint&)> &running,
					  const std::function<void(const QPoint&)> &actionTriggered,
					  const std::function<void(const QPoint&)> &inertanceStarted,
					  const int &refreshCycle) :
	QObject(widget),
	m_widget(widget),
	m_refreshCycle(refreshCycle),
	mf_stop(stop),
	mf_running(running),
	mf_actionTriggered(actionTriggered),
	mf_inertanceStarted(inertanceStarted)
{
	widget->setAttribute(Qt::WA_TransparentForMouseEvents, false);

	connect(&TouchFilter::instance(), &TouchFilter::pressMenu, this, [this](){
		m_isPressed = false;
	});
}

InBind::~InBind()
{

}

#define MOUSE_BUTTON_EVENT \
	auto mouseEvent = J_DCT(QMouseEvent*, event); \
	if( mouseEvent == nullptr or mouseEvent->button() != Qt::LeftButton ) \
		return false;

#define TEXT_INTERACTION_CHECK(w) \
({ \
	auto var = w->property("QTE_TextInteraction"); \
	if( var.isValid() and var.toBool() ) { \
		return false; \
	} else if( w->parentWidget() ) { \
		var = w->parentWidget()->property("QTE_TextInteraction"); \
		if( var.isValid() and var.toBool() ) { \
			return false; \
		} \
	} \
})

#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
# define _POS(mouseEvent)  mouseEvent->globalPos()
#else
# define _POS(mouseEvent)  mouseEvent->globalPosition().toPoint()
#endif

bool InBind::processEvent(QWidget *srcWidget, QEvent *event)
{
	Q_ASSERT(srcWidget);

	if( event->type() == QEvent::MouseButtonPress )
	{
		MOUSE_BUTTON_EVENT
		mouseLeftButtonPressed(m_widget->mapFromGlobal(_POS(mouseEvent)));
		m_cMove = 0;
	}

	else if( event->type() == QEvent::MouseMove )
	{
		TEXT_INTERACTION_CHECK(srcWidget);
		if( not m_isPressed )
			return false;

		auto mouseEvent = J_DCT(QMouseEvent*, event);
		if( mouseEvent == nullptr )
			return false;

		auto pos = m_widget->mapFromGlobal(_POS(mouseEvent));
		if( pos == m_lastPos )
			return false;

		mouseMove(pos);

#if QT_VERSION < QT_VERSION_CHECK(6,4,0)
		if( m_cMove == 0 )
			unfetters(srcWidget, pos);
#else
		if( m_cMove == 0 )
			unfetters(srcWidget, pos, m_widget->mapToGlobal(pos));
#endif
	}

	else if( event->type() == QEvent::MouseButtonRelease )
	{
		TEXT_INTERACTION_CHECK(srcWidget);
		if( not m_isPressed or m_cMove == 1 )
			return false;

		MOUSE_BUTTON_EVENT
		mouseLeftButtonReleased(m_widget->mapFromGlobal(_POS(mouseEvent)));
	}

	else if( event->type() == QEvent::Hide or event->type() == QEvent::Show )
	{
		stopRun();
		if( m_isPressed )
		{
			unfetters(srcWidget);
			m_isPressed = false;
		}
	}

	else if( event->type() == QEvent::Close )
	{
		if( m_isPressed )
		{
			unfetters(srcWidget);
			m_isPressed = false;
		}
	}

	return false;
}

inline bool InBind::isURLMing() const
{
	return m_timerID > 0;
}

void InBind::stopRun(bool delay)
{
	if( m_timerID <= 0 )
		return ;

	if( delay )
		m_delayStop = true;

	else
	{
		killTimer(m_timerID);
		m_timerID = -1;

		m_runEnabled = false;

		if( mf_stop )
			mf_stop();
	}
}

void InBind::timerEvent(QTimerEvent *event)
{
	QObject::timerEvent(event);
	if( event->timerId() != m_timerID )
		return ;

	if( not m_runEnabled )
		stopRun();
	else
		runExec();
}

inline void InBind::mouseLeftButtonPressed(const QPoint &pos)
{
	if( not m_isPressed )
	{
		m_isPressed = true;
		m_lastPos   = pos;

		if( m_runEnabled )
			stopRun();

		if( mf_actionTriggered )
			mf_actionTriggered(pos);
	}
}

inline void InBind::mouseLeftButtonReleased(const QPoint&)
{
	if( m_isPressed )
	{
		m_isPressed = false;

		if( m_runEnabled )
			startInertance();

		else if( mf_stop )
			mf_stop();
	}
}

inline void InBind::mouseMove(const QPoint &pos)
{
	if( not m_isPressed )
		return ;

	m_speed = m_lastPos - pos;

	if( m_speed.x() != 0 or m_speed.y() != 0 )
	{
		m_lastPos = pos;

		if( mf_running and mf_running(m_speed) )
			m_runEnabled = m_speed.x() < -2 or m_speed.x() > 2 or m_speed.y() < -2 or m_speed.y() > 2;
	}
}

#if QT_VERSION < QT_VERSION_CHECK(6,4,0)

void InBind::unfetters(QWidget *widget, const QPoint &pos)
{
	m_cMove = 1;
	widget->blockSignals(true);

	auto _event = new QMouseEvent(QEvent::MouseButtonRelease, pos, Qt::LeftButton,
								  Qt::LeftButton, Qt::NoModifier);

	QGuiApplication::sendEvent(widget, _event);
	delete _event;

	widget->blockSignals(false);
	m_cMove = 2;
}

#else

void InBind::unfetters(QWidget *widget, const QPoint &pos, const QPoint &globalPos)
{
	m_cMove = 1;
	widget->blockSignals(true);

	auto _event = new QMouseEvent(QEvent::MouseButtonRelease, pos, globalPos, Qt::LeftButton,
								  Qt::LeftButton, Qt::NoModifier);

	QGuiApplication::sendEvent(widget, _event);
	delete _event;

	widget->blockSignals(false);
	m_cMove = 2;
}

#endif

void InBind::startInertance()
{
	if( m_timerID > 0 )
		return ;

	else if( mf_inertanceStarted )
		mf_inertanceStarted(m_speed);

	m_delayStop = false;

#if defined(Q_OS_WIN) || defined(Q_OS_WIN32) || defined(Q_OS_WINDOWS)

	m_speed.rx() *= 5;
	m_speed.ry() *= 5;

#endif

	uint xSpeedNorm = qAbs(m_speed.x()); //x速度模
	uint ySpeedNorm = qAbs(m_speed.y()); //y速度模

	/*
		根据初速度来确定阻力的大小
		初速度越高，施加的阻力越大
		保证卷轴屏靠惯性运动的时间是一个确定值
	*/

	qreal time = 2.0 + J_SCT(qreal, xSpeedNorm) / m_widget->width() * 15.0;
	if( time > 3.2 )
		time = 3.2;

	m_acceleratedSpeed.rx() = xSpeedNorm / time / m_refreshCycle; //x轴方向加速度模
	if( m_acceleratedSpeed.x() <= 1 )
		m_acceleratedSpeed.rx() = 1;

	time = 2.0 + J_SCT(qreal, ySpeedNorm) / m_widget->height() * 15.0;
	if( time > 3.2 )
		time = 3.2;

	m_acceleratedSpeed.ry() = ySpeedNorm / time / m_refreshCycle; //y轴方向加速度模
	if( m_acceleratedSpeed.y() <= 1 )
		m_acceleratedSpeed.ry() = 1;

	// 尽量使两轴方向的运动时间一致
	uint xTime = xSpeedNorm / m_acceleratedSpeed.x();
	uint yTime = ySpeedNorm / m_acceleratedSpeed.y();

	if( xTime > yTime )
	{
		m_acceleratedSpeed.ry() = ySpeedNorm / xTime;
		if( m_acceleratedSpeed.y() <= 1 )
			m_acceleratedSpeed.ry() = 1;
	}

	else if( xTime < yTime )
	{
		m_acceleratedSpeed.rx() = xSpeedNorm / yTime;
		if( m_acceleratedSpeed.x() <= 1 )
			m_acceleratedSpeed.rx() = 1;
	}

	m_timerID = startTimer(m_refreshCycle, Qt::PreciseTimer);
	runExec();
}

bool InBind::runExec()
{
	// 运动
	if( mf_running and mf_running(m_speed) == false )
		m_delayStop = true;

	// x轴方向减速
	if( m_speed.x() > 0 )
	{
		m_speed.setX( m_speed.x() - m_acceleratedSpeed.x() );
		if( m_speed.x() < 0 )
			m_speed.setX(0);
	}

	else if( m_speed.x() < 0 )
	{
		m_speed.setX( m_speed.x() + m_acceleratedSpeed.x() );
		if( m_speed.x() > 0 )
			m_speed.setX(0);
	}

	// y轴方向减速
	if( m_speed.y() > 0 )
	{
		m_speed.setY( m_speed.y() - m_acceleratedSpeed.y() );
		if( m_speed.y() < 0 )
			m_speed.setY(0);
	}

	else if( m_speed.y() < 0 )
	{
		m_speed.setY( m_speed.y() + m_acceleratedSpeed.y() );
		if( m_speed.y() > 0 )
			m_speed.setY(0);
	}

	// 如果速度为0, 停止运动（定时器）
	if( m_speed.x() == 0 and m_speed.y() == 0 )
	{
		stopRun();
		return false;
	}

	else if( m_delayStop > 0 )
	{
		// 匀加加速度（加变速运动）
		m_acceleratedSpeed.setX( m_acceleratedSpeed.x() * 5 / 2 );
		m_acceleratedSpeed.setY( m_acceleratedSpeed.y() * 5 / 2 );
	}

	return true;
}

/*--------------------------------------------------------------------------------------------------------*/

class Q_DECL_HIDDEN AppEventFilter : public QObject
{
	J_VOLATILE_SINGLETON(AppEventFilter)

public:
	bool bind(const JUVLMEngine::Bind &action, const int &rate);
	void unbound(const QWidget *widget);

public:
	void stopMotionImmediately(const QWidget *widget);
	void stopMotion(const QWidget *widget);

public:
	bool isBind(const QWidget *widget) const;
	bool isURLMing(const QWidget *widget) const;

protected:
	bool eventFilter(QObject *watched, QEvent *event) override;

private:
	QHash<const QWidget*, InBind*> m_bindHash;

	QPointer<QWidget> m_lastObject {nullptr};
	QEvent::Type m_lastEventType = QEvent::None;
};
J_VOLATILE_SINGLETON_OBJECT(AppEventFilter)

AppEventFilter::AppEventFilter()
{
	if( qApp == nullptr )
	{
		qFatal("Error(lib:QTEGui): qApp is NULL.");
		abort();
	}

	qApp->installEventFilter(this);
}

AppEventFilter::~AppEventFilter()
{

}

bool AppEventFilter::bind(const JUVLMEngine::Bind &action, const int &rate)
{
	if( m_bindHash.find(action.widget) == m_bindHash.end() )
	{
		m_bindHash.insert(action.widget, new InBind(action.widget,
													action.stop,
													action.running,
													action.actionTriggered,
													action.inertanceStarted,
													rate));
		return true;
	}
	return false;
}

void AppEventFilter::unbound(const QWidget *widget)
{
	auto it = m_bindHash.find(widget);
	delete *it;

	m_bindHash.erase(it);
}

#define FIND_BIND(widget, ...) \
	if( widget == nullptr ) { \
		jError() << QString("Widget is nullptr (%1 : %2)").arg(__FILE__).arg(__LINE__); \
		return __VA_ARGS__; \
	} \
	auto it = m_bindHash.find(widget); \
	if( it == m_bindHash.end() ) \
		return __VA_ARGS__;

void AppEventFilter::stopMotionImmediately(const QWidget *widget)
{
	FIND_BIND(widget)
	(*it)->stopRun();
}

void AppEventFilter::stopMotion(const QWidget *widget)
{
	FIND_BIND(widget)
	(*it)->stopRun(true);
}

bool AppEventFilter::isBind(const QWidget *widget) const
{
	return m_bindHash.find(widget) == m_bindHash.end()? false : true;
}

bool AppEventFilter::isURLMing(const QWidget *widget) const
{
	FIND_BIND(widget, false)
	return (*it)->isURLMing();
}

bool AppEventFilter::eventFilter(QObject *watched, QEvent *event)
{
	auto widget = J_OCT(QWidget*, watched);
	if( widget == nullptr )
		return false;

	else if( event->type() != QEvent::MouseButtonPress and
			 event->type() != QEvent::MouseButtonRelease and
			 event->type() != QEvent::MouseMove and
			 event->type() != QEvent::Hide and
			 event->type() != QEvent::Show )
		return false;

	if( m_lastObject and m_lastEventType == event->type() )
	{
		if( watched == m_lastObject->parent() )
			return false;
	}

	m_lastObject = widget;
	m_lastEventType = event->type();

	auto wParent = widget;

	while( wParent != nullptr )
	{
		auto bind = m_bindHash.value(wParent, nullptr);

		if( bind )
			return bind->processEvent(widget, event);

		wParent = wParent->parentWidget();
	}

	return false;
}

/*--------------------------------------------------------------------------------------------------------*/

class Q_DECL_HIDDEN JUVLMEnginePrivate : public QObject
{
public:
	inline explicit JUVLMEnginePrivate(JUVLMEngine *q_ptr) :
		QObject(q_ptr) {}

	QSet<const QWidget*> m_widgetSet;
	int m_refreshCycle = 20;
};

/*--------------------------------------------------------------------------------------------------------*/

JUVLMEngine::JUVLMEngine(QObject *parent) :
	QObject(parent),
	d_ptr(new JUVLMEnginePrivate(this))
{

}

JUVLMEngine::~JUVLMEngine()
{
	for(auto &widget : d_ptr->m_widgetSet)
		AppEventFilter::instance().unbound(widget);
}

void JUVLMEngine::bind(const Bind &action)
{
	if( action.widget == nullptr )
	{
		jError() << QString("Widget is nullptr (%1 : %2)").arg(__FILE__).arg(__LINE__);
		return ;
	}

	else if( AppEventFilter::instance().bind(action, d_ptr->m_refreshCycle) )
	{
		d_ptr->m_widgetSet.insert(action.widget);

		connect(action.widget, &QWidget::destroyed, this, [this](QObject *obj){
			unbound(J_RCT(QWidget*, obj));
		});
	}
}

void JUVLMEngine::unbound(const QWidget *widget)
{
	AppEventFilter::instance().unbound(widget);
	d_ptr->m_widgetSet.remove(widget);
}

void JUVLMEngine::stopMotionImmediately(const QWidget *widget)
{
	AppEventFilter::instance().stopMotionImmediately(widget);
}

void JUVLMEngine::stopMotion(const QWidget *widget)
{
	AppEventFilter::instance().stopMotion(widget);
}

void JUVLMEngine::setRefreshRate(int rate)
{
	if( rate < 20 )
		rate = 20;

	else if( rate > 75 )
		rate = 75;

	d_ptr->m_refreshCycle = J_SCT(int, 1000.0 / J_SCT(qreal, rate) + 0.5);
}

bool JUVLMEngine::isBind(const QWidget *widget) const
{
	return AppEventFilter::instance().isBind(widget);
}

bool JUVLMEngine::isURLMing(const QWidget *widget) const
{
	return AppEventFilter::instance().isURLMing(widget);
}

int JUVLMEngine::refreshRate() const
{
	return 1000 / d_ptr->m_refreshCycle;
}

JUVLMEngine &JUVLMEngine::globalInstance()
{
	static JUVLMEngine obj;
	return obj;
}

} //namespace QTE
